import random

import numpy as np


class MGA:
    def __init__(self, crossover_prob=0.8, mutation_prob=0.1, pop_size=10, chrom_size=8, select_ratio=0.01,
                 generation=500):
        self._crossover_prob = crossover_prob
        self._mutation_prob = mutation_prob
        self._pop_size = pop_size
        self._chrom_size = chrom_size
        self._select_ratio = select_ratio
        self._generation = generation
        self._population = []

    def get_max_fitness(self):
        max_fitness = -1
        for index, value in enumerate(self._population):
            each_fitness = self.cal_fitness(value)
            if each_fitness >= max_fitness:
                max_fitness = each_fitness
            else:
                pass
        return max_fitness

    # 族群初始化
    def init_population(self, row_num=10, col_num=8):
        population = np.zeros([row_num, col_num], int)
        for index in range(0, row_num):
            for _index in range(0, col_num):
                population[index, _index] = np.random.choice([0, 1])
        self._population = population
        pass

    # 计算适应度
    def cal_fitness(self, input, error=0.01):
        fitness_val = 1
        fitness_val = np.sum(input) * 2
        return fitness_val

    # 生成pop_size个新对象，并在生成的时候染色体进行交叉和随机产生变异
    def crossover_and_mutation(self):
        new_population_list = []
        # 前一个和后一个交叉变异
        for father in self._population:
            child = father
            # 产生子代时不是必然发生交叉，而是以一定的概率发生交叉
            if np.random.rand() < self._crossover_prob:
                # 随机找一个母亲
                mother = self._population[np.random.randint(self._pop_size)]
                child = self.cross(child, mother)

            else:
                pass
            child = self.mutation(child)
            new_population_list.append(child)

        new_population = np.asarray(new_population_list)
        # 重新填充
        self._population = new_population.copy()
        pass

    # 变异，这里选择的是二进制的变异
    def mutation(self, chromosome):
        if np.random.rand() < self._mutation_prob:
            # 随机产生一个实数，代表要变异基因的位置
            mutate_points = np.random.randint(0, self._chrom_size)
            # 将变异点位置的二进制反转
            chromosome[mutate_points] = chromosome[mutate_points] ^ 1
        return chromosome

    # 交叉
    def cross(self, child, mother):
        cross_points = np.random.randint(low=1, high=self._chrom_size)
        child[cross_points:] = mother[cross_points:]
        return child

    # 评估和选择，使用轮盘赌，返回选择的在population中的个体
    def select(self):
        # 每个个体适应度
        every_fitness = []
        for i in range(self._pop_size):
            each_every_fitness_value = self.cal_fitness(self._population[i])
            every_fitness.append(each_every_fitness_value)
        # 所有的适应度
        total_fitness = sum(every_fitness)
        ids = np.random.choice(np.arange(self._pop_size), size=self._pop_size, replace=True,
                               p=every_fitness / total_fitness)

        new_population = self._population[ids]
        self._population = new_population.copy()

    # 开始执行进化过程
    def run(self):
        # 先初始化种群
        self.init_population(row_num=self._pop_size, col_num=self._chrom_size)
        for each_gen in range(0, self._generation):
            # 选择两个最好的染色体
            self.select()
            # 两个染色体交叉、变异，并重新填充population族群
            self.crossover_and_mutation()
            # 获取最大的值
            max_fitness = self.get_max_fitness()
            # 打印
            print('each_gen' + str(each_gen) + ',' + str(max_fitness))

        pass


if __name__ == '__main__':
    mga = MGA(crossover_prob=0.8, mutation_prob=0.1, pop_size=10, chrom_size=8, select_ratio=0.1, generation=1000)
    mga.run()

    pass
